package com.lyl.pipeline.handler.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.lyl.pipeline.annotation.PipeNode;
import com.lyl.pipeline.annotation.PipeNodeHandler;
import com.lyl.pipeline.constant.HandlerType;
import com.lyl.pipeline.core.PipelineContext;
import com.lyl.pipeline.exception.PipelineException;
import com.lyl.pipeline.handler.BasePipeNodeHandler;
import com.lyl.pipeline.utils.ContextHolderUtils;
import com.lyl.pipeline.utils.PipeNodeUtil;
import com.lyl.pipeline.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.script.RedisScript;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @author Mr.Luo
 * @date 2022-08-01 10:31
 * @desc ip池处理器
 */
@Slf4j
@PipeNodeHandler(HandlerType.IP_POOL_LIMITER)
public class IpPoolLimiterHandler implements BasePipeNodeHandler {
    //key的生成策略： prefix:url名
    public static final String IP_BLACKLIST_PREFIX = "ip_blacklist:";
    @Resource
    private RedisScript<Boolean> ipPoolLimiterScript;
    //存放所有的handler参数
    private final Map<String, Map<String, Object>> allParamMap = new HashMap<>();

    @Override
    public Object invoke(Object o, PipeNode pipeNode, PipelineContext pipelineContext) {
        //1.获取请求IP
        HttpServletRequest request = ContextHolderUtils.getRequest();
        String ip = PipeNodeUtil.getIp(request);

        //redis的键
        String ipBlackListKey = IP_BLACKLIST_PREFIX + request.getRequestURI();
        String methodIPKey = ipBlackListKey + ":" + ip;

        //获取pipeNode中自定义参数
        Assert.notEmpty(pipeNode.params(), "[" + request.getRequestURI() + "]请求的ip池处理器的自定义参数不能为空");
        String paramStr = Arrays.toString(pipeNode.params());
        String paramKey = "pipeNode:" + DigestUtil.md5Hex(paramStr);

        //将pipeNode的自定义参数解析之后的数据放入allParamMap中
        Map<String, Object> paramMap = allParamMap.getOrDefault(paramKey, new HashMap<>());
        if (ObjectUtil.isEmpty(paramMap)) {
            synchronized (this) {
                if (!allParamMap.containsKey(paramKey)) {
                    allParamMap.put(paramKey, PipeNodeUtil.getParamMap(pipeNode));
                }
            }
            paramMap = allParamMap.get(paramKey);
        }

        long time = Convert.toLong(Optional.ofNullable(paramMap.get("time")).orElseThrow(() -> new PipelineException("ip池处理器的time参数不能为空")));
        long count = Convert.toLong(Optional.ofNullable(paramMap.get("count")).orElseThrow(() -> new PipelineException("ip池处理器的count参数不能为空")));

        //判断当前是否被限制，如果限制返回true,否则返回false
        boolean isLimit = RedisUtil.execute(ipPoolLimiterScript, ListUtil.toList(ipBlackListKey, methodIPKey), ip, count, time, getTimeStamp());
        Assert.isFalse(isLimit,() -> new PipelineException("当前ip已被限制"));
        return pipelineContext.next();
    }

    /**
     * 获取不重复的时间戳
     *
     * @return 时间戳
     */
    private synchronized long getTimeStamp() {
        long millis = System.currentTimeMillis();
        ThreadUtil.safeSleep(1);
        return millis;
    }


}
